{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "SpamCommentsModelMaker.ipynb",
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "# Training a Comment-spam Detection model with TensorFlow Lite Model Maker\n",
        "\n",
        "## Learning objectives\n",
        "\n",
        "1. Install TensorFlow Lite Model Maker.\n",
        "2. Download the data from the Colab server to your device.\n",
        "3. Use a data loader to make the training data.\n",
        "4. Build the model.\n",
        "\n",
        "## Overview\n",
        "\n",
        "In this lab, you review code created with TensorFlow and TensorFlow Lite Model Maker to create a model with a dataset based on comment spam. The original data is available on Kaggle. It's been gathered into a single CSV file, and cleaned up by removing broken text, markup, repeated words and more. This will make it easier to focus on the model instead of the text.\n",
        "\n",
        "Each learning objective will correspond to a __#TODO__ in this student lab notebook -- try to complete this notebook first and then review the [solution notebook](../solutions/spam_comments_model_maker.ipynb).\n"
      ],
      "metadata": {
        "id": "0TGK-JagTW6x"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Install TensorFlow Lite Model Maker"
      ],
      "metadata": {
        "id": "-6ZNPPQSXxtf"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Awq8sMaIFHJ1"
      },
      "source": [
        "# Install Model maker\n",
        "!pip install -q tflite-model-maker &> /dev/null"
      ],
      "execution_count": 1,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "**Note:** After the installation, restart the kernel by clicking **Kernel > Restart kernel > Restart**."
      ],
      "metadata": {
        "id": "yM_LIWp_YAQD"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Import the code\n",
        "\n",
        "Import the necessary dependencies and check the Tensorflow version:"
      ],
      "metadata": {
        "id": "yM_LIWp_YAQD"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "2fEAqoLLF6O9"
      },
      "source": [
        "# Imports and check that we are using TF2.x\n",
        "import numpy as np\n",
        "import os\n",
        "\n",
        "from tflite_model_maker import configs\n",
        "from tflite_model_maker import ExportFormat\n",
        "from tflite_model_maker import model_spec\n",
        "from tflite_model_maker import text_classifier\n",
        "from tflite_model_maker.text_classifier import DataLoader\n",
        "\n",
        "import tensorflow as tf\n",
        "assert tf.__version__.startswith('2')\n",
        "tf.get_logger().setLevel('ERROR')"
      ],
      "execution_count": 1,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Download the dataset\n",
        "\n",
        "Next you'll download the data from the lab server to your device, and set the `data_file` variable to point at the local file:"
      ],
      "metadata": {
        "id": "ueIscTgCYydX"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "lsuDZvjgREsS",
        "outputId": "af4f0b3e-eee0-403b-aefb-a99bceed18d2",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "# Download the dataset as a CSV and store as data_file\n",
        "data_file = tf.keras.utils.get_file(fname='comment-spam.csv', origin='https://storage.googleapis.com/laurencemoroney-blog.appspot.com/lmblog_comments.csv', extract=False)"
      ],
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Downloading data from https://storage.googleapis.com/laurencemoroney-blog.appspot.com/lmblog_comments.csv\n",
            "237568/235543 [==============================] - 0s 0us/step\n",
            "245760/235543 [===============================] - 0s 0us/step\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Use a model spec from model maker\n",
        "\n",
        "When you use pre-learned embeddings, you get to start with a corpus, or collection, of words that have already had sentiment learned from a large body of text, so you get to a solution much faster than when you start from zero.\n",
        "\n",
        "Model Maker provides several pre-learned embeddings that you can use, but the simplest and quickest one to begin with is average_word_vec option."
      ],
      "metadata": {
        "id": "Jywe1j1sZWiD"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "pbew43TbG9HQ"
      },
      "source": [
        "# TODO 1\n",
        "# Use a model spec from model maker. Options are 'mobilebert_classifier', 'bert_classifier' and 'average_word_vec'\n",
        "# The first 2 use the BERT model, which is accurate, but larger and slower to train\n",
        "# Average Word Vec is kinda like transfer learning where there are pre-trained word weights\n",
        "# and dictionaries\n",
        "spec = # TODO 1: Your code goes here\n",
        "spec.num_words = 2000\n",
        "spec.seq_len = 20\n",
        "spec.wordvec_dim = 7"
      ],
      "execution_count": 3,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "#### The num_words parameter\n",
        "\n",
        "You also specify the number of words that you want your model to use.\n",
        "You might think \"the more the better\", but there's generally a right number based on the frequency that each word is used. If you use every word in the entire corpus, the model could try to learn and establish the direction of words that are only used once. In any text corpus, many words are only used once or twice, so their inclusion in your model isn't worthwhile because they have a negligible impact on the overall sentiment.\n",
        "You can use the `num_words` parameter to tune your model based on the number of words that you want. A smaller number might provide a smaller and quicker model, but it could be less accurate because it recognizes fewer words. On the other hand, a larger number might provide a larger and slower model. It's important to find the sweet spot!\n",
        "\n",
        "#### The wordvec_dim parameter\n",
        "\n",
        "The `wordved_dim` parameter is the number of dimensions that you want to use for the vector for each word. The rule of thumb determined from research is that it's the fourth root of the number of words. For example, if you use 2,000 words, 7 is a good starting point. If you change the number of words that you use, you can also change this.\n",
        "\n",
        "#### The seq_len parameter\n",
        "\n",
        "Models are generally very rigid when it comes to input values. For a language model, this means that the language model can classify sentences of a particular static length. That's determined by the `seq_len` parameter or sequence length.\n",
        "When you convert words into numbers or tokens, a sentence then becomes a sequence of these tokens. In this case, your model is trained to classify and recognize sentences with 20 tokens. If the sentence is longer than this, it's truncated. If it's shorter, it's padded. You can see a dedicated `<PAD>` token in the corpus that's used for this."
      ],
      "metadata": {
        "id": "Jywe1j1sZWiD"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Earlier you downloaded the CSV file. Now it's time to use a data loader to turn this into training data that the model can recognize:"
      ],
      "metadata": {
        "id": "B0HKhCKZZ2h7"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "-WdQmzTKHFVn"
      },
      "source": [
        "# TODO 2\n",
        "# Load the CSV using DataLoader.from_csv to make the training_data\n",
        "data = # TODO 2: Your code goes here(\n",
        "      filename=data_file,\n",
        "      text_column='commenttext',\n",
        "      label_column='spam',\n",
        "      model_spec=spec,\n",
        "      delimiter=',',\n",
        "      shuffle=True,\n",
        "      is_training=True)\n",
        "\n",
        "train_data, test_data = data.split(0.9)"
      ],
      "execution_count": 4,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "If you open the CSV file in an editor, you'll see that each line just has two values, and these are described with text in the first line of the file. Typically, each entry is then deemed to be a column.\n",
        "\n",
        "You'll see that the descriptor for the first column is `commenttext`, and that the first entry on each line is the text of the comment. Similarly, the descriptor for the second column is `spam`, and you'll see that the second entry on each line is `True` or `False`, to denote if that text is considered comment spam or not. The other properties set the `model_spec` variable that you created earlier, along with a delimiter character, which in this case is a comma as the file is comma separated. You will use this data for training the model, so `is_Training` is set to `True`.\n",
        "\n",
        "You will want to hold back a portion of the data for testing the model. Split the data, with 90% of it for training, and the other 10% for testing/evaluation. Because we're doing this we want to make sure that the testing data is chosen at random, and isn't the ‘bottom' 10% of the dataset, so you use `shuffle=True` when loading the data to randomize it."
      ],
      "metadata": {
        "id": "B0HKhCKZZ2h7"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Build the model"
      ],
      "metadata": {
        "id": "B0HKhCKZZ2h7"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "qThBoIIyG_Du",
        "outputId": "6fb4ae90-1108-42ff-c3bb-4bd974c219e4",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "# TODO 3\n",
        "# Build the model\n",
        "model = # TODO 3: Your code goes here"
      ],
      "execution_count": 5,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "28/28 [==============================] - 3s 28ms/step - loss: 0.5993 - accuracy: 0.6942 - val_loss: 0.5483 - val_accuracy: 0.7200\n",
            "Epoch 2/2\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.5150 - accuracy: 0.7020 - val_loss: 0.4695 - val_accuracy: 0.7600\n",
            "Epoch 3/3\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.4472 - accuracy: 0.8013 - val_loss: 0.4024 - val_accuracy: 0.8400\n",
            "Epoch 4/4\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.3796 - accuracy: 0.8962 - val_loss: 0.3456 - val_accuracy: 0.9400\n",
            "Epoch 5/5\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.3247 - accuracy: 0.9408 - val_loss: 0.2997 - val_accuracy: 0.9400\n",
            "Epoch 6/6\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.2877 - accuracy: 0.9386 - val_loss: 0.2642 - val_accuracy: 0.9600\n",
            "Epoch 7/7\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.2475 - accuracy: 0.9498 - val_loss: 0.2366 - val_accuracy: 0.9400\n",
            "Epoch 8/8\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.2222 - accuracy: 0.9509 - val_loss: 0.2162 - val_accuracy: 0.9300\n",
            "Epoch 9/9\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.2001 - accuracy: 0.9453 - val_loss: 0.2008 - val_accuracy: 0.9300\n",
            "Epoch 10/10\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1829 - accuracy: 0.9554 - val_loss: 0.1881 - val_accuracy: 0.9300\n",
            "Epoch 11/11\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1679 - accuracy: 0.9509 - val_loss: 0.1780 - val_accuracy: 0.9300\n",
            "Epoch 12/12\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1556 - accuracy: 0.9509 - val_loss: 0.1697 - val_accuracy: 0.9400\n",
            "Epoch 13/13\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.1506 - accuracy: 0.9587 - val_loss: 0.1621 - val_accuracy: 0.9400\n",
            "Epoch 14/14\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1495 - accuracy: 0.9554 - val_loss: 0.1556 - val_accuracy: 0.9400\n",
            "Epoch 15/15\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1378 - accuracy: 0.9576 - val_loss: 0.1504 - val_accuracy: 0.9500\n",
            "Epoch 16/16\n",
            "28/28 [==============================] - 0s 10ms/step - loss: 0.1267 - accuracy: 0.9609 - val_loss: 0.1454 - val_accuracy: 0.9500\n",
            "Epoch 17/17\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1268 - accuracy: 0.9654 - val_loss: 0.1408 - val_accuracy: 0.9500\n",
            "Epoch 18/18\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1116 - accuracy: 0.9643 - val_loss: 0.1372 - val_accuracy: 0.9500\n",
            "Epoch 19/19\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1108 - accuracy: 0.9676 - val_loss: 0.1332 - val_accuracy: 0.9500\n",
            "Epoch 20/20\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.1141 - accuracy: 0.9699 - val_loss: 0.1286 - val_accuracy: 0.9600\n",
            "Epoch 21/21\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.1013 - accuracy: 0.9676 - val_loss: 0.1253 - val_accuracy: 0.9600\n",
            "Epoch 22/22\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0899 - accuracy: 0.9766 - val_loss: 0.1222 - val_accuracy: 0.9600\n",
            "Epoch 23/23\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0956 - accuracy: 0.9699 - val_loss: 0.1184 - val_accuracy: 0.9600\n",
            "Epoch 24/24\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0928 - accuracy: 0.9777 - val_loss: 0.1155 - val_accuracy: 0.9600\n",
            "Epoch 25/25\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0864 - accuracy: 0.9754 - val_loss: 0.1127 - val_accuracy: 0.9600\n",
            "Epoch 26/26\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0854 - accuracy: 0.9766 - val_loss: 0.1103 - val_accuracy: 0.9600\n",
            "Epoch 27/27\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0884 - accuracy: 0.9743 - val_loss: 0.1073 - val_accuracy: 0.9600\n",
            "Epoch 28/28\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0748 - accuracy: 0.9799 - val_loss: 0.1042 - val_accuracy: 0.9600\n",
            "Epoch 29/29\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0774 - accuracy: 0.9810 - val_loss: 0.1020 - val_accuracy: 0.9600\n",
            "Epoch 30/30\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0769 - accuracy: 0.9821 - val_loss: 0.0991 - val_accuracy: 0.9600\n",
            "Epoch 31/31\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0730 - accuracy: 0.9799 - val_loss: 0.0966 - val_accuracy: 0.9600\n",
            "Epoch 32/32\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0674 - accuracy: 0.9844 - val_loss: 0.0946 - val_accuracy: 0.9600\n",
            "Epoch 33/33\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0650 - accuracy: 0.9799 - val_loss: 0.0915 - val_accuracy: 0.9600\n",
            "Epoch 34/34\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0597 - accuracy: 0.9833 - val_loss: 0.0901 - val_accuracy: 0.9600\n",
            "Epoch 35/35\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0614 - accuracy: 0.9844 - val_loss: 0.0877 - val_accuracy: 0.9600\n",
            "Epoch 36/36\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0527 - accuracy: 0.9888 - val_loss: 0.0859 - val_accuracy: 0.9700\n",
            "Epoch 37/37\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0506 - accuracy: 0.9888 - val_loss: 0.0840 - val_accuracy: 0.9700\n",
            "Epoch 38/38\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0566 - accuracy: 0.9866 - val_loss: 0.0815 - val_accuracy: 0.9700\n",
            "Epoch 39/39\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0548 - accuracy: 0.9900 - val_loss: 0.0800 - val_accuracy: 0.9700\n",
            "Epoch 40/40\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0490 - accuracy: 0.9866 - val_loss: 0.0786 - val_accuracy: 0.9700\n",
            "Epoch 41/41\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0477 - accuracy: 0.9866 - val_loss: 0.0772 - val_accuracy: 0.9700\n",
            "Epoch 42/42\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0427 - accuracy: 0.9866 - val_loss: 0.0760 - val_accuracy: 0.9700\n",
            "Epoch 43/43\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0471 - accuracy: 0.9866 - val_loss: 0.0745 - val_accuracy: 0.9700\n",
            "Epoch 44/44\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0352 - accuracy: 0.9911 - val_loss: 0.0737 - val_accuracy: 0.9700\n",
            "Epoch 45/45\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0390 - accuracy: 0.9888 - val_loss: 0.0724 - val_accuracy: 0.9700\n",
            "Epoch 46/46\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0376 - accuracy: 0.9888 - val_loss: 0.0712 - val_accuracy: 0.9700\n",
            "Epoch 47/47\n",
            "28/28 [==============================] - 0s 9ms/step - loss: 0.0360 - accuracy: 0.9888 - val_loss: 0.0707 - val_accuracy: 0.9800\n",
            "Epoch 48/48\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0336 - accuracy: 0.9888 - val_loss: 0.0702 - val_accuracy: 0.9800\n",
            "Epoch 49/49\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0313 - accuracy: 0.9888 - val_loss: 0.0702 - val_accuracy: 0.9800\n",
            "Epoch 50/50\n",
            "28/28 [==============================] - 0s 8ms/step - loss: 0.0274 - accuracy: 0.9888 - val_loss: 0.0698 - val_accuracy: 0.9800\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "This code creates a text-classifier model with Model Maker and you specify the training data that you want to use as set up in fourth step), the model specification as set up in the fourth step, and a number of epochs, which is 50 in this case."
      ],
      "metadata": {
        "id": "o9xLk4yOby6K"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "5QXEwKYxdPzs",
        "outputId": "7310986f-df47-44e2-f59e-4fd086d80a6c",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "loss, accuracy = model.evaluate(train_data)"
      ],
      "execution_count": 6,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "29/29 [==============================] - 0s 4ms/step - loss: 0.0191 - accuracy: 0.9933\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Export a model\n",
        "\n",
        "Export a model to SavedModel format with the model, vocabulary and labels. Run this cell to specify a directory and export the model:"
      ],
      "metadata": {
        "id": "o9xLk4yOby6K"
      }
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "In-1-rzW-_9b"
      },
      "source": [
        "# This will export to SavedModel format with the model, vocabulary and labels.\n",
        "model.export(export_dir='/mm_spam_savedmodel/', export_format=[ExportFormat.LABEL, ExportFormat.VOCAB, ExportFormat.SAVED_MODEL])"
      ],
      "execution_count": 7,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "Compress the entire folder of `/mm_spam_savedmodel` and down the generated `mm_spam_savedmodel.zip` file:"
      ],
      "metadata": {
        "id": "o9xLk4yOby6K"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# TODO 4\n",
        "# Rename the SavedModel subfolder to a version number\n",
        "!mv /mm_spam_savedmodel/saved_model /mm_spam_savedmodel/123\n",
        "# TODO 4: Your code goes here"
      ],
      "metadata": {
        "id": "mIhBu3pI_2Xz",
        "outputId": "1681da51-453f-4720-bab9-6169912b1b42",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "  adding: mm_spam_savedmodel/ (stored 0%)\n",
            "  adding: mm_spam_savedmodel/vocab.txt (deflated 47%)\n",
            "  adding: mm_spam_savedmodel/labels.txt (stored 0%)\n",
            "  adding: mm_spam_savedmodel/123/ (stored 0%)\n",
            "  adding: mm_spam_savedmodel/123/saved_model.pb (deflated 87%)\n",
            "  adding: mm_spam_savedmodel/123/variables/ (stored 0%)\n",
            "  adding: mm_spam_savedmodel/123/variables/variables.data-00000-of-00001 (deflated 35%)\n",
            "  adding: mm_spam_savedmodel/123/variables/variables.index (deflated 59%)\n",
            "  adding: mm_spam_savedmodel/123/assets/ (stored 0%)\n",
            "  adding: mm_spam_savedmodel/123/keras_metadata.pb (deflated 86%)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "3UNNrCWbKbxh",
        "outputId": "9b0d17fc-4fc1-45c2-daf8-b4f25ef5c9e2",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 17
        }
      },
      "source": [
        "# Optional extra\n",
        "# You can use this cell to export details for projector.tensorflow.org\n",
        "# Where you can explore the embeddings that were learned for this dataset\n",
        "embeddings = model.model.layers[0]\n",
        "weights = embeddings.get_weights()[0]\n",
        "tokenizer = model.model_spec.vocab\n",
        "\n",
        "import io\n",
        "\n",
        "out_v = io.open('vecs.tsv', 'w', encoding='utf-8')\n",
        "out_m = io.open('meta.tsv', 'w', encoding='utf-8')\n",
        "for word in tokenizer:\n",
        "  #word = tokenizer.decode([word_num])\n",
        "  value = tokenizer[word]\n",
        "  embeddings = weights[value]\n",
        "  out_m.write(word + \"\\n\")\n",
        "  out_v.write('\\t'.join([str(x) for x in embeddings]) + \"\\n\")\n",
        "out_v.close()\n",
        "out_m.close()\n",
        "\n",
        "\n",
        "try:\n",
        "  from google.colab import files\n",
        "except ImportError:\n",
        "  pass\n",
        "else:\n",
        "  files.download('vecs.tsv')\n",
        "  files.download('meta.tsv')"
      ],
      "execution_count": 9,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<IPython.core.display.Javascript object>"
            ],
            "application/javascript": [
              "\n",
              "    async function download(id, filename, size) {\n",
              "      if (!google.colab.kernel.accessAllowed) {\n",
              "        return;\n",
              "      }\n",
              "      const div = document.createElement('div');\n",
              "      const label = document.createElement('label');\n",
              "      label.textContent = `Downloading \"${filename}\": `;\n",
              "      div.appendChild(label);\n",
              "      const progress = document.createElement('progress');\n",
              "      progress.max = size;\n",
              "      div.appendChild(progress);\n",
              "      document.body.appendChild(div);\n",
              "\n",
              "      const buffers = [];\n",
              "      let downloaded = 0;\n",
              "\n",
              "      const channel = await google.colab.kernel.comms.open(id);\n",
              "      // Send a message to notify the kernel that we're ready.\n",
              "      channel.send({})\n",
              "\n",
              "      for await (const message of channel.messages) {\n",
              "        // Send a message to notify the kernel that we're ready.\n",
              "        channel.send({})\n",
              "        if (message.buffers) {\n",
              "          for (const buffer of message.buffers) {\n",
              "            buffers.push(buffer);\n",
              "            downloaded += buffer.byteLength;\n",
              "            progress.value = downloaded;\n",
              "          }\n",
              "        }\n",
              "      }\n",
              "      const blob = new Blob(buffers, {type: 'application/binary'});\n",
              "      const a = document.createElement('a');\n",
              "      a.href = window.URL.createObjectURL(blob);\n",
              "      a.download = filename;\n",
              "      div.appendChild(a);\n",
              "      a.click();\n",
              "      div.remove();\n",
              "    }\n",
              "  "
            ]
          },
          "metadata": {}
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<IPython.core.display.Javascript object>"
            ],
            "application/javascript": [
              "download(\"download_cf4148cd-ee36-4e5e-958d-a7f36e7e89e1\", \"vecs.tsv\", 167556)"
            ]
          },
          "metadata": {}
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<IPython.core.display.Javascript object>"
            ],
            "application/javascript": [
              "\n",
              "    async function download(id, filename, size) {\n",
              "      if (!google.colab.kernel.accessAllowed) {\n",
              "        return;\n",
              "      }\n",
              "      const div = document.createElement('div');\n",
              "      const label = document.createElement('label');\n",
              "      label.textContent = `Downloading \"${filename}\": `;\n",
              "      div.appendChild(label);\n",
              "      const progress = document.createElement('progress');\n",
              "      progress.max = size;\n",
              "      div.appendChild(progress);\n",
              "      document.body.appendChild(div);\n",
              "\n",
              "      const buffers = [];\n",
              "      let downloaded = 0;\n",
              "\n",
              "      const channel = await google.colab.kernel.comms.open(id);\n",
              "      // Send a message to notify the kernel that we're ready.\n",
              "      channel.send({})\n",
              "\n",
              "      for await (const message of channel.messages) {\n",
              "        // Send a message to notify the kernel that we're ready.\n",
              "        channel.send({})\n",
              "        if (message.buffers) {\n",
              "          for (const buffer of message.buffers) {\n",
              "            buffers.push(buffer);\n",
              "            downloaded += buffer.byteLength;\n",
              "            progress.value = downloaded;\n",
              "          }\n",
              "        }\n",
              "      }\n",
              "      const blob = new Blob(buffers, {type: 'application/binary'});\n",
              "      const a = document.createElement('a');\n",
              "      a.href = window.URL.createObjectURL(blob);\n",
              "      a.download = filename;\n",
              "      div.appendChild(a);\n",
              "      a.click();\n",
              "      div.remove();\n",
              "    }\n",
              "  "
            ]
          },
          "metadata": {}
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<IPython.core.display.Javascript object>"
            ],
            "application/javascript": [
              "download(\"download_2aedd15b-bdc9-4864-8937-083b8c87ca6e\", \"meta.tsv\", 12646)"
            ]
          },
          "metadata": {}
        }
      ]
    }
  ]
}
